﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

using SharedGenomics.Core;
using SharedGenomics.Annotations;

namespace SharedGenomics.Workbench.Annotations
{
    /// <summary>
    /// An instance of this class specified the dependency relationships between the various <see cref="AnnotationKey"/>s which correspond to annotations in the 
    /// workbench. The graph is a collection of <see cref="AnnotationKeyDependencyGraphNode"/>s - each node has a number of predecessor and successor nodes which 
    /// represent the annotations direct dependents and pre-requisites. Note instances of this class are immutable.
    /// </summary>
    //NOTE: This is current a tree rather than a 'proper' graph since it annotations currently have up to one possible dependent, but in principle they could 
    //have more than one so this option is left open. At the moment, if multiple edges are added which gives a node multiple parents then the last edge will 
    //'win' and be the parent node.
    public class AnnotationKeyDependencyGraph : IEnumerable<AnnotationKeyDependencyNode>
    {
        private IDictionary<AnnotationKey, AnnotationKeyDependencyNode> _keyNodeMap;

        public AnnotationKeyDependencyGraph(IEnumerable<AnnotationKeyDependencyEdge> edges)
        {
            this._keyNodeMap = new Dictionary<AnnotationKey, AnnotationKeyDependencyNode>();

            //go through each edge and add it to this graph
            //ignore any null edges
            foreach (var edge in (edges ?? Enumerable.Empty<AnnotationKeyDependencyEdge>()).Where(e => e != null))
            {
                this.AddEdge(edge);
            }
        }

        private void AddEdge(AnnotationKeyDependencyEdge edge)
        {
            Debug.Assert(edge != null);

            //add nodes for source and destination if they haven't already been added
            var sourceNode = this.ObtainNode(edge.Source);
            var destNode = this.ObtainNode(edge.Destiation);

            //set the destination parent to the source
            destNode.Parent = sourceNode;

            //add the destination to the source's children
            sourceNode.AddChild(destNode);
        }

        /// <summary>
        /// Obtains a node for the given key - if the a node for the key already exists, it is returned. If no such node has been added then a new one 
        /// is created and added before being returned
        /// </summary>
        /// <param name="key"></param>
        /// <returns>An <see cref="AnnotationKeyDependencyNode"/> for the given key</returns>
        private AnnotationKeyDependencyNode ObtainNode(AnnotationKey key)
        {
            Debug.Assert(key != null);

            //fetch node for key if already exists, otherwise create a new node and add it
            if (!this._keyNodeMap.ContainsKey(key))
            {
                this._keyNodeMap.Add(key, new AnnotationKeyDependencyNode(key));
            }

            return this._keyNodeMap[key];
        }

        public AnnotationKeyDependencyNode GetNodeForKey(AnnotationKey key)
        {
            key.ThrowIfNull("key");
            ExceptionHelper.ThrowIf<ArgumentException>(!this._keyNodeMap.ContainsKey(key), String.Format("No node with value {0} was found in the graph", key.Key));

            return this._keyNodeMap[key];
        }

        public int NodeCount
        {
            get { return this._keyNodeMap.Count; }
        }

        public IEnumerator<AnnotationKeyDependencyNode> GetEnumerator()
        {
            return this._keyNodeMap.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}
